#include <core.h>
#include <mantle.h>
#include <crust.h>
#include <_pubcrt.h>
#include "cfile.h"

ECode GetFileInterface(IDiskManager *pDiskMgr,
    const wchar_t *wszFileName, IBasicFile** ppFile)
{
    IBasicDirectory* pDir = NULL;
    ECode ec;

    wchar_t *pwszFullPath = new wchar_t[_MAX_PATH];
    if (!pwszFullPath) {
        return E_OUT_OF_MEMORY;
    }

    if (FAILED(WFullPath(pwszFullPath, wszFileName, _MAX_PATH))) {
        delete pwszFullPath;
        return E_INVALID_ARGUMENT;
    }

    WStringBuf *pwsbFileName = WStringBuf::Alloc(_MAX_FNAME);
    if (!pwsbFileName) {
        delete pwszFullPath;
        return E_OUT_OF_MEMORY;
    }

    ec = pDiskMgr->AcquireDirInterface(pwszFullPath, &pDir, pwsbFileName);
    if (FAILED(ec)) {
        delete pwszFullPath;
        WStringBuf::Free(pwsbFileName);
        return ec;
    }

    if (pwsbFileName->GetLength()) {
        ec = pDir->Lookup(*pwsbFileName, ppFile);
    }
    else {
        ec = E_INVALID_ARGUMENT;
    }

    pDir->Release();
    if (FAILED(ec)) {
        delete pwszFullPath;
    }

    WStringBuf::Free(pwsbFileName);

    return ec;
}

ECode GetModuleFromFileSystem(const wchar_t *wszName, IAbsoluteIO **ppIAbsoluteIO, Int32 *psize)
{
    assert(ppIAbsoluteIO);
    assert(psize);

    IDiskManager *pDiskMgr = NULL;
    IBasicFile *iSrcFile = NULL;
    IAbsoluteIO *iAbsoluteIO = NULL;
    Int32 fileSize;
    ECode ec;

    //first for the file in the current working directory
    ec = DzFindService(L"diskmgr", (PInterface *)&pDiskMgr);
    if (FAILED(ec)) goto ErrorExit;

    ec = GetFileInterface(pDiskMgr, wszName, &iSrcFile);
    if (FAILED(ec)) {
        if (IS_PATH_SEPARATOR(wszName[0]) || wszName[1] == L':') {
            goto ErrorExit;
        }

        //through the directories specified by the environment variable.
        wchar_t *pPathEnv;
        pPathEnv = WGetEnv(L"PATH");
        if (pPathEnv == NULL) {
            goto ErrorExit;
        }

        wchar_t *path = new wchar_t[_MAX_PATH];
        if (!path) {
            goto ErrorExit;
        }

        wchar_t *pPos;
        Boolean bEnd = FALSE;
        int nlen;
        while (!bEnd) {
            if ((pPos = wcschr(pPathEnv, L';')) != NULL) {
                nlen = pPos - pPathEnv;
                nlen = ((nlen < _MAX_PATH) ? nlen : _MAX_PATH);
                wcsncpy(path, pPathEnv, nlen);
                path[nlen] = L'\0';
                if (*(pPos + 1) != L'\0')
                    pPathEnv = pPos + 1;
                else
                    bEnd = TRUE;
            }
            else {
                wcsncpy(path, pPathEnv, _MAX_PATH);
                path[_MAX_PATH] = L'\0';
                bEnd = TRUE;
            }

            if (wcslen(path) + wcslen(wszName) + 1 >= _MAX_PATH) continue;
            wcscat(path, WCS_PATH_SEPARATOR);
            wcscat(path, wszName);
            ec = GetFileInterface(pDiskMgr, path, &iSrcFile);
            if (ec == NOERROR) break;
        }
        delete path;
        if (FAILED(ec)) goto ErrorExit;
    }

    iAbsoluteIO = IAbsoluteIO::Probe(iSrcFile);
    if (!iAbsoluteIO) {
        ec = E_NO_INTERFACE;
        goto ErrorExit;
    }
    iAbsoluteIO->AddRef();

    ec = iAbsoluteIO->GetSize(&fileSize);
    if (FAILED(ec)) {
        iAbsoluteIO->Release();
        goto ErrorExit;
    }

    *ppIAbsoluteIO = iAbsoluteIO;
    *psize = fileSize;

ErrorExit:
    if (pDiskMgr) pDiskMgr->Release();
    if (iSrcFile) iSrcFile->Release();

    return ec;
}


ECode CFile::Init(const wchar_t *wszName)
{
    assert(wszName);
    ECode ec;
    Int32 len;
    Byte * pImage, * pImageLimit;

    ec = GetBootModule(wszName, (void **)&pImage, (void **)&pImageLimit);
    if (FAILED(ec)) {
        IAbsoluteIO *pIAbsoluteIO;
        Int32 size;
        ec = GetModuleFromFileSystem(wszName, &pIAbsoluteIO, &size);
        if (FAILED(ec)) {
            if (E_OUT_OF_MEMORY == ec)
                return E_OUT_OF_MEMORY;
            else
                return E_FILE_NOT_FOUND;
        }
        m_FileType = LOADED_FILE_TYPE_FILESYS;
        m_File.pIAbsoluteIO = pIAbsoluteIO;
        m_FileSize = size;
    }
    else {
        len = wcslen(wszName) + 1;
        m_FileType = LOADED_FILE_TYPE_BMFS;
        m_File.pBMFile = pImage;
        m_FileSize = pImageLimit - pImage;
    }

    return ec;
}



CFile::~CFile()
{
    switch (m_FileType) {
        case LOADED_FILE_TYPE_FILESYS:
            m_File.pIAbsoluteIO->Release();
            break;

        case LOADED_FILE_TYPE_BMFS:

        default:
            break;
    }

}


ECode CFile::Write(UInt32 offset, UInt32 size, Void *pBuf)
{
    assert(offset + size <= m_FileSize);
    assert(offset + size >= size);

    ECode ec;
    UInt32 wsize, writeCount, limit, wresult;

    switch (m_FileType) {
        case LOADED_FILE_TYPE_BMFS:
            ec = E_FAIL;
            break;

        case LOADED_FILE_TYPE_FILESYS:
            limit = offset + size;
            wsize = 0;
            while (offset < limit) {
                writeCount = (limit - offset) > MAX_MSH_SIZE
                    ? MAX_MSH_SIZE : limit - offset;
                MemoryBuf ebBox((Byte*)pBuf + wsize, writeCount);

                ec = m_File.pIAbsoluteIO->Write(offset, ebBox, (Int32 *)&wresult);
                if (FAILED(ec)) {
                    break;
                }
                assert(writeCount == wresult);
                wsize += writeCount;
                offset += MAX_MSH_SIZE;
            }
            assert(wsize == size);
            ec = NOERROR;
            break;

        default:
            ec = E_FAIL;
    }

    return ec;
}

ECode CFile::Read(UInt32 offset, UInt32 size, Void *pBuf)
{
//    assert(ppBuf);
    assert(offset + size <= m_FileSize);
    assert(offset + size >= size);

    UInt32 rsize, readCount, limit;
    ECode ec;
//    Byte* pBuf;
//
//    pBuf = (Byte*)*ppBuf;

    // read file
    switch (m_FileType) {
        case LOADED_FILE_TYPE_BMFS:
            memcpy(pBuf, m_File.pBMFile + offset, size);
            ec = NOERROR;
            break;

        case LOADED_FILE_TYPE_FILESYS:
            #if 0
            MemoryBuf ebBox(pBuf, size);
            ec = iAbsoluteIO->Read(0, size, &ebBox);
            if (FAILED(ec)) break;
            #else
            // TODO: change to read one time.
            limit = offset + size;
            rsize = 0;
            while (offset < limit) {
                readCount = (limit - offset) > MAX_MSH_SIZE
                    ? MAX_MSH_SIZE : limit - offset;
                MemoryBuf ebBox((Byte*)pBuf + rsize, readCount);
                ec = m_File.pIAbsoluteIO->Read(offset, readCount, &ebBox);
                if (FAILED(ec)) {
                    break;
                }
                rsize += readCount;
                offset += MAX_MSH_SIZE;
            }
            #endif
            ec = NOERROR;
            break;

        default:
            ec = E_FAIL;
    }

    return ec;
}


